<?Php

    /**
     * набор классов и функций для всяких всячин
     *
     * class preferences                    - синглетон для хранения настроек
     * class authorisation                  - авторизация
     * class urlParser                      - магический парсер url
     * class database
     *
     * function go_to($url)                 - header+meta
     * function ekran($var)                 - экранирование переменных
     *
     * function cacheMe($key, $data, $expires)
     * function getFromCache($key)
     * function cp1251_utf8($sInput)
     * function translit($var)              - транслит кирилицы в латиницу
     * function strip_tags_attributes($string,$allowtags=NULL,$allowattributes=NULL)
     * function convert_line_breaks($string, $line_break=PHP_EOL)
     */


    /**
     * синглетон для хранения настроек
     * http://ru.wikipedia.org/wiki/Одиночка_(шаблон_проектирования)
     */
    class preferences{
        private $props=array();
        private static $instance;
        
        private function __construct(){}
        
        public static function getInstance(){
            if (empty(self::$instance)){
                self::$instance=new preferences();
            }
            return self::$instance;
        }
        
        public function setProperty($key, $val){
            $this->props[$key]=$val;
        }
        
        public function getProperty($key){
            return $this->props[$key];
        }
    } // preferences
    
    
    
    /**
     * авторизация
     */
    class authorisation{
        
        public static function checkAuth(){
            $pref = preferences::getInstance();
            $pdo = $pref -> getProperty('pdo');
            $loggedin=false; $priv['user']=0; $priv['privileges']=0; $account=0;
            
            if (!empty($_COOKIE[session_name()])){
                session_id() || session_start();
                    
                // если авторизованы
                if ($_SESSION['auth'] != ''){
                    $e = select_row($pdo, DB_MODEL_USERS, '*', "`" . AUTH_FIELD . "`='".ekran($_SESSION[AUTH_FIELD])."'");
                }
            }
            
            // если не авторизованы
            if (!$e){
                if ($_COOKIE[AUTH_FIELD] != ''){ // есть данные в кукисах
                    $e = select_row($pdo, DB_MODEL_USERS, '*', "`" . AUTH_FIELD . "`='".ekran($_COOKIE[AUTH_FIELD])."' AND `md5`='".ekran($_COOKIE['pass'])."'", '', 10);
                }
                if ($_REQUEST['username'] != ''){ // логинимся
                    $e = select_row($pdo, DB_MODEL_USERS, '*', "`" . AUTH_FIELD . "`='".ekran($_REQUEST['username'])."' AND `md5`='".md5($_REQUEST['password'])."'", '', 10);
                    $flag=true;
                    
                    if (!$e){ // попытка №2 - если пароль в SSHA
                        $e = select_row($pdo, DB_MODEL_USERS, '*', "`" . AUTH_FIELD . "`='".ekran($_REQUEST['username'])."'", '', 10);
                        $hash = '{SSHA}'.base64_encode(pack("H*" , $e['md5']));
                        $ohash = base64_decode(substr($hash, 6)); $osalt = substr($ohash, 20);  $ohash = substr($ohash, 0, 20);
                        $nhash = pack("H*", sha1($_REQUEST['password'] . $osalt));
                        if ($ohash != $nhash)unset($e);
                    }
                }
            }
            
            // если есть такой юзверь
            if ($e['id'] > 0 AND $_REQUEST['action']!='logout'){
                    $account = $e['id'];
                    $priv['user'] = true;
                    $priv['email'] = $e['email'];
                    $priv['nick'] = $e['nick'];
                    $priv['privileges'] = $e['privileges'];
                    session_id() || session_start();
                    $_SESSION['auth'] = 'true'; $_SESSION[AUTH_FIELD] = $e[AUTH_FIELD]; $_SESSION['pass'] = $e['md5'];
                    if ($flag){
                        setcookie(AUTH_FIELD,$e[AUTH_FIELD],time()+60*60*24*60,'/');
                        setcookie('login',$e['nick'],time()+60*60*24*60,'/');
                        setcookie('pass',$e['md5'],time()+60*60*24*60,'/');
                        setcookie('lastVisit',time());
                    }
                    update_table($pdo, DB_MODEL_USERS, array('last_visit'=>date('Y-m-d H:i:s')), "`id`='{$account}'");
            }
            else
            {
                unset($_SESSION['auth']); unset($_SESSION[AUTH_FIELD]); unset($_SESSION['pass']);
                if (!empty($_COOKIE[session_name()])){
                    setcookie(AUTH_FIELD,'',time(),'/'); setcookie('pass','',time(),'/');
                    setcookie('lastVisit','',time(),'/'); setcookie('login','',time(),'/');
                    setcookie(session_name(),'',time(),'/');
                    session_destroy();
                }
                if ($flag){
                    $pref = preferences::getInstance();
                    $pref -> setProperty('messageString','Ошибка авторизации! Попробуйте ввести логин и пароль ещё раз!');
                }
            }
            
            // вбиваем полученные знания в синглетон
            $pref -> setProperty ('loginData', array('priv' => $priv, 'account' => $account));
        }
        
    } // authorisation
    
    
    
    
    /**
     * парсер URL, встречается магия
     */
    class urlParser{
        
        private $uri, $phpSelf, $pref;
        public $section, $htmlstatecode, $error;
        
        public function __construct($uri, $phpSelf){
            $this -> uri = $uri;
            $this -> phpSelf = $phpSelf;
            $this -> pref = preferences::getInstance();
            $this -> pdo = $this -> pref -> getProperty('pdo');
            $this -> loginData = $this -> pref -> getProperty('loginData');
        }
        
        public function parseUrl(){
            
            //if ($_SERVER['REDIRECT_URL']) $uri = $_SERVER['REDIRECT_URL']; // для mod_rewrite
            $out = str_replace('/index.php','',$this -> phpSelf);
            $uri = str_replace($out,'',$this -> uri);
            $rootPath = '/' . str_replace($uri,'',$this -> uri) . '/';
            $rootPath = str_replace('//','/',$rootPath); $rootPath = rtrim($rootPath,'/');
            if (defined('ROOT_PATH'))$rootPath=ROOT_PATH;
            if ($rootPath=='/')$rootPath='';
            
            $uri = explode('?', $uri); $url = strtolower($uri[0]); $params = $uri[1];  // отсекли параметры
            $url = trim($url, '/');  // отсекли слеши по концам
            $urla = explode('/', $url);
            
            // здесь лежат некие действия с url'ом, например, определение языка - и т.д.
            
            // определили язык страницы
            $lang = 'rus';
            if ($urla[0] == 'en'){ $lang = 'eng'; unset($urla[0]); }
            $this -> pref -> setProperty ( 'pageLang', $lang );
            
            // после всех магических действий - собрали url обратно
            $url = ekran(implode('/',$urla));
            if ($url == '')$url = 'index';
            $this -> pref -> setProperty('url', $url);
            $this -> pref -> setProperty('rootPath', $rootPath);
            $this -> pref -> setProperty('params', $params);
            
            
            // ищем оригинальные контентные страницы в базе данных
            $res = select_data($this -> pdo, DB_MODEL_PAGES . ' AS pa', '*', "('{$url}' LIKE concat(`url`,'%') OR `url` LIKE ('%{$url}%'))
                                        " . makePrivQuery($this -> loginData['priv']['privileges'], $this -> loginData['account']) . "
                                        AND `enabled`=1",
                                        "`exactly_privileges` DESC, `visits` DESC"); // магия
            
            // ищем точный адрес страницы - без учёта привелегий и языка
            $resLogin = select_data($this -> pdo, DB_MODEL_PAGES, '*', "'{$url}' LIKE concat(`url`,'%') AND `enabled`=1", "visits DESC");
            
            $singleURL = false;
            foreach( $res as $k=>$value ){
                if ( $value['url'] == $url AND $value['lang'] == $lang ){
                    $singleURL = $value; $spId = $value['pid']; $urltext = $value['url'];
                }
                else if ( $value['stand_alone'] == 0 ){
                    $lurl = '';
                    foreach( $urla as $uu ){
                        $lurl .= '/'. $uu; $lurl = trim ($lurl, '/');
                        if ( $value['url'] == $lurl AND $value['lang'] == $lang ){
                            $singleURL = $value; $spId = $value['pid']; $urltext = $value['url'];
                        }
                    }
                    unset($res[$k]);
                }
            }
            
            
            // найдена одна конкретная страница
            if ($singleURL){
                $this -> section = $singleURL['id'];
                if (ENABLE_VISITS_LOGING=='true')
                    $this -> pdo -> query ("UPDATE " . DB_MODEL_PAGES . " SET `visits`=`visits`+1 WHERE `id`={$singleURL['id']}");
                $this -> pref -> setProperty ( 'urlParams', trim ( str_replace( $urltext, '', $url ), '/' ) );
                $this -> pref -> setProperty ( 'pageId', $singleURL['id'] );
                $this -> pref -> setProperty ( 'parentId', $spId );
                return;
            }
            
            // найдено несколько вариантов страниц
            // или "возможно, вы имели в виду что-то другое"
            if (sizeof($res)>0 AND $singleURL===false){
                if ($lang == 'rus')$this->error='такая страница не найдена!<BR>возможно, Вы имели в виду одну из этих:';
                if ($lang == 'eng')$this->error='can not find requested page!<BR>may be, You are looking for one of theese:';
                foreach($res as $value){
                    $adl = ''; if ($value['lang']=='eng')$adl = 'en/';
                    $this -> error .= "<BR><a href='/{$adl}{$value['url']}'>{$value['header']}</a> [{$value['lang']}]";
                }
                $this -> error .= '<BR><BR>';
                $this -> htmlstatecode = 404;
                return;
            }
            
            // если дожили до этого места, значит, и страницу не нашли, и похожих страниц не нашли
            $this -> error = true;
            if (sizeof($resLogin)==0)$this -> htmlstatecode = 404;
            if (sizeof($resLogin)>0)$this -> htmlstatecode = 403;
            
        }
        
    }
    
    
    function makePrivQuery($priv, $acc){
        return "AND 
                (
                    (pa.`required_privileges`<=" . $priv . " AND pa.`exactly_privileges`=0)
                    OR
                    (pa.`required_privileges`=" . $priv . " AND pa.`exactly_privileges`=1)
                    OR 
                    pa.`plugin_id` IS NOT NULL
                )
                AND (( pa.`plugin_id` IS NULL) OR (EXISTS (
                    SELECT * FROM " . DB_MODEL_PRIVILEGES . " AS mp WHERE
                        (
                            (mp.`action`='access_all')
                            OR
                            (mp.`action`='access_regged' AND " . $acc . ">0)
                            OR
                            (mp.`action`='access_user' AND " . $acc . " = mp.`user_id`)
                            OR
                            (pa.`required_privileges`=0)
                        )
                        AND mp.`plugin_id`=pa.`plugin_id`
                        AND (
                                (pa.`required_privileges`<=mp.`privileges` AND pa.`exactly_privileges`=0)
                                OR
                                (pa.`required_privileges`=mp.`privileges` AND pa.`exactly_privileges`=1)
                            )
                )))";
    }
    
    
    
    function go_to($url){
            @header('Location: '.$url);
            die('<meta http-equiv="Refresh" content="0; URL='.$url.'">');
    }
    
    
    /**
     * экранируем переменные
     * если передаётся массив, то экранируются все его элементы
     */
    function ekran($var){ 
        if (is_array($var)){
            foreach ($var as $k => $v){
                $var[$k] = ekran($v);
            }
        }
        else
        {
            $var = htmlspecialchars($var);
            $var = addslashes($var);
            $var = strip_tags($var);
        }
        return($var);
    }



    // обёртка для базы данных
    class database{
        
        public function __construct($pdo){
            $this -> pdo = $pdo;
            
            $pref = preferences::getInstance();
            $this -> memcache = $pref -> getProperty('memcache');
        }
        
        /**
         * получаем данные из таблицы
         *
         * метод выглядит таким страшным, потому что в нем учтена возможность
         * подвисания мемкеша, а также разница в синтаксисе mysql/pgsql
         */
        public function sel($table, $fields='*', $statement = "1", $order='', $expires=3600,
                         $limit='', $offset='', $vartitle='', $debug=DEBUG_MODE, $nocache=false){
            $beg = microtime(true);
            if (!class_exists('Memcached') AND !class_exists('Memcache')) $nocache = true;
            $qu = "SELECT {$fields} FROM {$table} WHERE {$statement}";
            if ($order)$qu .= " ORDER BY {$order}"; 
            if ($limit + $offset>0){ $qu .= " LIMIT "; $dl=''; if ($limit!=='' AND $offset!=='')$dl=',';
                if (DB_SERVER == 'mysql')$qu .= "{$offset}{$dl}{$limit}";
                if (DB_SERVER == 'pgsql')$qu .= "{$limit}{$dl}{$offset}";
            }
            // сформировали запрос
            
            $a = " /* '{$_SERVER['REQUEST_URI']}' " . date('Y-m-d H:i') . " */"; 
            $qu .= $a; // добавляем комментарий в тело запроса
            
            $req = md5($table . $fields . $statement . $order . $limit);
            if ($vartitle) $req = $vartitle; // для строгого указания имени переменной
            if (!$nocache){ // если НЕ указано НЕ пользоваться кешем
                $obj = $this -> memcache -> getFromCache($req);
                
                if (!is_array($obj)){
                    if ($debug){echo '<BR>'.$qu.'<BR>'; $status = 'nonCached';}
                    $q = $this -> pdo -> query($qu);
                    $obj = $q -> fetchall( PDO::FETCH_ASSOC );
                    $this -> memcache -> cacheMe($req, $obj, $expires);
                }
                else if ($debug){echo '<BR>'.$qu.'<BR>';  $status = 'cached';}
            }
            else {
                if ($debug){echo '<BR>'.$qu.'<BR>'; $status = 'NOTtoCache';}
                $q = $this -> pdo -> query($qu);
                $obj = $q -> fetchall( PDO::FETCH_ASSOC );
            }
            if ($debug)$this -> log_stdOut_query($beg, $status, $vartitle);
            return $obj;
            
        }
        
        public function sel_row($table, $fields='*', $statement = "1", $order='', $expires=3600,
                         $limit='', $offset='', $vartitle='', $debug=DEBUG_MODE, $nocache=false){
            $a = $this -> sel($table, $fields, $statement, $order, $expires,
                         $limit, $offset, $vartitle, $debug, $nocache);
            return $a[0];
            
        }
        
        public function ins($table, $var = array(), $debug=DEBUG_MODE, $notProceed=false){
            if (!is_array($var)){$buf = $this -> pdo; $this -> pdo = $var; $var = $buf;} // для совместимости со старыми версиями
            $beg = microtime(true);
            $values = "";
            $str = "";
            $i = 0;
            if(sizeof($var)){
                foreach($var as $k=>$v){
                    $i++;
                    $values.="`".$k."`";
                    if ($v!==null)$str.="'".addslashes($v)."'"; else $str.="null";
                    if($i<sizeof($var)){
                            $values.=", ";
                            $str.=", ";
                    }
                }
                $a = " /* '{$_SERVER['REQUEST_URI']}' " . date('Y-m-d H:i') . " */"; 
                $q=("INSERT INTO ".$table." (".$values.") VALUES (".$str.")") . $a;
                if ($debug)echo '<BR>'.$q.'<BR>';
                if (!$notProceed)$this -> pdo -> query($q);
            }
            if (!$notProceed)$aa=$this -> pdo -> lastInsertId(); else $aa=$q;
            if ($debug)$this -> log_stdOut_query($beg);
            return $aa;
        }
        
        public function upd($table, $var = array(), $statement = "1", $debug=DEBUG_MODE){
            if (!is_array($var)){$buf = $this -> pdo; $this -> pdo = $var; $var = $buf;} // для совместимости со старыми версиями
            $beg = microtime(true);
            $i = 0;
            $str = "";
            if(sizeof($var)){
                foreach($var as $k=>$v){
                    $i++;
                    if ($v!==null)$str.="`".$k."` = '".addslashes($v)."'";
                    else $str.="`".$k."` = null";
                    if($i<sizeof($var))$str.=", ";
                }
                $a = " /* '{$_SERVER['REQUEST_URI']}' " . date('Y-m-d H:i') . " */"; 
                $q = "UPDATE ".$table." SET ".$str." WHERE ".$statement . $a;
                if ($debug)echo '<BR>'.$q.'<BR>';
                $this -> pdo->query($q);
            }
            if ($debug)$this -> log_stdOut_query($beg);
            return(true);
        }
        
        
        /**
        * показываем в output запросы
        */
        function log_stdOut_query($begtime, $status='NOTtoCache', $vartitle=''){
            $end = microtime(true) - $begtime; // замер производительности
            if ($status == 'nonCached')$status = '<b style="color: red;">nonCached</b>';
            if ($status == 'cached')$status = '<b style="color: green;">cached</b>';
            if ($status == 'NOTtoCache')$status = '<b style="color: blue;">NOTtoCache</b>';
            
            global $MCqnum; $MCqnum++;
            $tt = (round(10000*$end)/10); $a=''; if ($tt>10)$a='style="color: red;"';
            
            if ($vartitle)$adx=", title: <b>{$vartitle}</b>";
            echo $status . ': <b '.$a.'>' . $tt . '</b> мсек, запрос №<b>'.$MCqnum.'</b>,
                                lifetime: <b>'.$lifetime.'</b>' . $adx . '<BR>';
        }
    }
    
    
    // обёртка для мемкеша
    class mc{
        
        function __construct(){
            $mem = new Memcache();
            $mem -> connect('localhost', 11211);
        }
        
        function cacheMe($key, $data, $expires){
            $this -> mem -> set($key, $data, false, $expires);
        }
        
        function getFromCache($key){
            $this -> da = $mem -> get($key);
            return $da;
        }
        
        function unCacheMe($key){
            $this -> mem -> delete($key);
        }
    }



    // набор обёрток к функциям базы данных
    // сохранён для совместимости со старым функционалом
    function select_data($pdo, $table, $fields='*', $statement = "1", $order='', $expires=3600,
                         $limit='', $offset='', $vartitle='', $debug=DEBUG_MODE, $nocache=false){
        $pref = preferences::getInstance(); $db = $pref -> getProperty('db');
        return $db -> sel($table, $fields, $statement, $order, $expires,
                         $limit, $offset, $vartitle, $debug, $nocache);
    }
    
    function select_row($pdo, $table, $fields='*', $statement = "1", $order='', $expires=3600,
                         $limit='', $offset='', $vartitle='', $debug=DEBUG_MODE, $nocache=false){
        $pref = preferences::getInstance(); $db = $pref -> getProperty('db');
        return $db -> sel_row($table, $fields, $statement, $order, $expires,
                         $limit, $offset, $vartitle, $debug, $nocache);
    }
    
    
    function insert_into($pdo, $table, $var = array(), $debug=DEBUG_MODE, $notProceed=false){
        $pref = preferences::getInstance(); $db = $pref -> getProperty('db');
        return $db -> ins($table, $var, $debug, $notProceed);
    }
    
    function update_table($pdo, $table, $var = array(), $statement = "1", $debug=DEBUG_MODE){
        $pref = preferences::getInstance(); $db = $pref -> getProperty('db');
        return $db -> upd($table, $var, $statement, $debug);
    }
    
    

    function cp1251_utf8($string){
        $string = iconv('cp1251', 'utf-8', $string);
        return $string;
    }


    /**
     * транслит кирилицы в латиницу
     */
    function translit($var){
            $var = urldecode($var);
            $str = array(' '=>'_','!'=>'','@'=>'','$'=>'','%'=>'','^'=>'','&'=>'','*'=>'','('=>'',')'=>'',
                         '-'=>'_','|'=>'','['=>'',']'=>'','}'=>'','{'=>'','`'=>'','~'=>'','='=>'','а'=>'a',
                         'б'=>'b','в'=>'v','г'=>'g','д'=>'d','е'=>'e','ё'=>'e','ж'=>'zh','з'=>'z','и'=>'i',
                         'й'=>'y','к'=>'k','л'=>'l','м'=>'m','н'=>'n','о'=>'o','п'=>'p','р'=>'r','с'=>'s',
                         'т'=>'t','у'=>'u','ф'=>'f','х'=>'h','ц'=>'c','ч'=>'ch','ш'=>'sh','щ'=>'sh','ъ'=>'',
                         'ы'=>'y','ь'=>'','э'=>'e','ю'=>'yu','я'=>'ya','А'=>'A','Б'=>'B','В'=>'V','Г'=>'G',
                         'Д'=>'D','Е'=>'E','Ё'=>'E','Ж'=>'ZH','З'=>'Z','И'=>'I','Й'=>'Y','К'=>'K','Л'=>'L',
                         'М'=>'M','Н'=>'N','О'=>'O','П'=>'P','Р'=>'R','С'=>'S','Т'=>'T','У'=>'U','Ф'=>'F',
                         'Х'=>'H','Ц'=>'C','Ч'=>'CH','Ш'=>'SH','Щ'=>'SH','Ъ'=>'','Ы'=>'Y','Ь'=>'','Э'=>'E',
                         'Ю'=>'YU','Я'=>'YA',
                         '1'=>'1','2'=>'2','3'=>'3','4'=>'4','5'=>'5','6'=>'6','7'=>'7','8'=>'8','9'=>'9','0'=>'0','.'=>'.','j'=>'j',
                         'q'=>'q','w'=>'w','x'=>'x','Q'=>'Q','W'=>'W','X'=>'X','J'=>'J');
            $f='';
            for ($e=0; $e<strlen($var); $e++){
                    $a=mb_substr($var,$e,1,'UTF-8');
                    if (in_array($a,$str))
                    {
                            $f.=$a;
                    }
                    elseif (in_array($a,array_keys($str)))
                    {
                            $f.=$str[$a];
                    }
            }
            $var=$f;
            $var=strtr($var,$str);
            return $var;
    }
    
    
    
    
    
    function strip_tags_attributes($string,$allowtags=NULL,$allowattributes=NULL){ 
        $string = strip_tags($string,$allowtags); 
        if (!is_null($allowattributes)) { 
            if(!is_array($allowattributes)) 
                $allowattributes = explode(",",$allowattributes); 
            if(is_array($allowattributes)) 
                $allowattributes = implode(")(?<!",$allowattributes); 
            if (strlen($allowattributes) > 0) 
                $allowattributes = "(?<!".$allowattributes.")"; 
            $string = preg_replace_callback("/<[^>]*>/i",create_function( 
                '$matches', 
                'return preg_replace("/ [^ =]*'.$allowattributes.'=(\"[^\"]*\"|\'[^\']*\')/i", "", $matches[0]);'    
            ),$string); 
        } 
        return $string; 
    }
    
    
    
    
    
    function convert_line_breaks($string, $line_break=PHP_EOL) {
        $patterns = array(    
                            "/(<br>|<br \/>|<br\/>)\s*/i",
                            "/(\r\n|\r|\n)/"
        );
        $replacements = array(    
                                PHP_EOL,
                                $line_break
        );
        $string = preg_replace($patterns, $replacements, $string);
        return $string;
    }





?>